Refactoring Backlog — Unified Insurance Ontology

23 stories · 69 story points · every story sized at 3 SP · Claim epics first, then Policy & cross-cutting. Each story states the defect, the fix, and testable acceptance criteria. Import-ready CSV: 02-backlog-user-stories.csv · Analysis: domain knowledge

EpicStoriesSPTheme
EPIC-CLM-A Claim core structureCLM-1 … CLM-618Separate loss from demand; lifecycle events; SKOS code lists
EPIC-CLM-B Claim financials & relationsCLM-7 … CLM-1218MoneyAmount pattern; broken properties; roles; subrogation
EPIC-CLM-C Claim integrity & governanceCLM-13 … CLM-1715Reinsurance, LossHistory de-stringing, SHACL suite, namespaces, CQ regression
EPIC-POL-D Policy & cross-cuttingPOL-1 … POL-618Domain errors, god-class decomposition, cancellation, roots, provenance
EPIC-CLM-A

Claim core structure — model the demand, the loss, and the lifecycle as different things

Resolves findings F-01, F-04, F-05, F-06 (part). Everything here follows the project's claim competency-question model and the codelist-SKOS standard.

CLM-1

Split ins:Claim into claim:Claim (demand) and claim:Loss (occurrence)

High3 SPclaim-core
As an ontology engineer, I want the claim demand and the loss occurrence modelled as two linked classes so that one loss can support multiple claims and loss facts are stated once, on the thing that actually happened.

Defect. ins:Claim ⊑ gist:Event makes the demand be the occurrence. ACORD (Claim vs ClaimsOccurrence), the OMG P&C model, and the project's own claim CQ vocabulary (claim:Claim + claim:Loss + claim:hasLoss) all separate them. Today "where did it happen" and "what caused it" are properties of the paperwork.

Fix. Introduce claim:Loss ⊑ core:Event; re-parent claim:Claim under core:Case (administrative case, aligned skos:closeMatch to ACORD Claim); add claim:hasLoss (Claim → Loss) with inverse claim:lossOfClaim declared via owl:inverseOf; annotate both classes with skos:definition and standard crosswalks.

Acceptance criteria

  • AC: claim:Loss exists as core:Event subclass with skos:definition, rdfs:isDefinedBy, and skos:closeMatch to ACORD ClaimsOccurrence recorded as an annotation.
  • AC: claim:Claim is no longer a subclass of any Event class; its definition reads as a demand for indemnification under a policy.
  • AC: claim:hasLoss declared with domain Claim / range Loss and a single owl:inverseOf axiom; no duplicate hand-written inverse remains.
  • AC: SPARQL "two claims sharing one loss" returns both claims against a single Loss IRI in the sample dataset.
  • AC: Reasoner (HermiT/ELK) run over the module reports consistency; Claim and Loss are declared disjoint.
Refs: ACORD Claims · OMG P&C · project claim-competency-questions · Blocks: CLM-2, CLM-3, CLM-4
CLM-2

Re-home loss-centric properties from Claim onto claim:Loss

High3 SPclaim-core
As a claims data consumer, I want location, peril and loss description carried by the Loss so that occurrence facts are not duplicated on every claim that arises from the same event.

Defect. ins:claimLocation → ins:Location, ins:claimPeril → ins:CoveredPeril, and ins:claimDescription hang off the demand. Two claims from one warehouse fire would each restate (and can contradict) the fire's facts.

Fix. Move as claim:lossLocation (Loss → core:Place / GeoSPARQL feature), claim:causedByPeril (Loss → peril concept), claim:lossDate, claim:lossDescription. Deprecate the old properties with owl:deprecated true + dct:isReplacedBy and provide a SPARQL CONSTRUCT migration for existing instance data.

Acceptance criteria

  • AC: No location, peril, or loss-date property has claim:Claim in its domain after the change.
  • AC: Deprecated ins:claimLocation/claimPeril/claimDescription carry owl:deprecated true and dct:isReplacedBy pointing at the new terms.
  • AC: The migration CONSTRUCT converts every legacy triple in the sample loss-run dataset with zero unmapped remainder (checked by count query).
  • AC: CQ "which claims arose from losses at location X caused by peril Y?" answers via Loss traversal in the regression pack (CLM-17).
Depends: CLM-1 · Refs: GeoSPARQL · message-encoding-antipattern
CLM-3

Model catastrophe as a first-class event; fix the wrongly-mandatory cat code

Medium3 SPclaim-core
As a cat-exposure analyst, I want losses linked to a shared CatastropheEvent so that cat aggregation is a graph traversal instead of string-matching PCS codes claim by claim.

Defect. ins:catastropheEventCode : xsd:string is mandatory on every claim — non-cat claims must fabricate a value — while isCatastropheRelated is optional; the PCS event itself has no identity, so "all claims for Hurricane X" is a string join.

Fix. Add claim:CatastropheEvent ⊑ core:Event with claim:catEventCode (PCS/internal, skos:notation-backed) and claim:partOfCatastrophe (Loss → CatastropheEvent, 0..1). Drop the boolean (derivable from link presence). SHACL: cat code required on the CatastropheEvent only.

Acceptance criteria

  • AC: catastropheEventCode and isCatastropheRelated are removed from Claim (deprecated with replacement annotations).
  • AC: A non-cat claim in the sample data validates with no catastrophe triples at all.
  • AC: Two losses linked to one CatastropheEvent aggregate via a single SPARQL GROUP BY in the regression pack.
  • AC: SHACL shape requires exactly one catEventCode per CatastropheEvent and rejects a cat link whose target lacks a code (seeded bad-data case fails with a named violation).
Depends: CLM-1 · Refs: PCS coding practice · rebuttal-cardinality-restrictions
CLM-4

Replace lifecycle date literals with ClaimEvent subclasses (FNOL / Opening / Closing / Reopening)

High3 SPclaim-lifecycle
As a claims operations analyst, I want each lifecycle transition recorded as a timestamped event so that reopen counts, cycle times and audit trails are queryable — not lost in three overwritable date fields.

Defect. dateReported/dateOpened/dateClosed literals cannot represent a claim closed twice, reopened once; project CQ-C10 ("which claims have been reopened?") and CQ-C11 (status from most recent event) are unanswerable.

Fix. Add claim:ClaimEvent ⊑ core:Event with pairwise-disjoint subclasses FnolEvent, OpeningEvent, ClosingEvent, ReopeningEvent (per the CQ vocabulary), each with time:hasBeginning/time:inXSDDateTimeStamp and prov:wasAssociatedWith the acting party; claim:hasClaimEvent (Claim → ClaimEvent). Keep the three date literals as derived, deprecated convenience terms during migration.

Acceptance criteria

  • AC: The four lifecycle subclasses exist, are pairwise disjoint (owl:disjointWith or AllDisjointClasses), and CQ-C9's co-assertion check returns no rows on valid data.
  • AC: Every ClaimEvent in sample data carries exactly one OWL-Time instant (SHACL-enforced) and an associated agent via PROV-O.
  • AC: CQ-C10 and CQ-C11 SPARQL from the project doc run unmodified against the new module and return the expected rows.
  • AC: A claim with Closing → Reopening → Closing history round-trips correctly; "current status = latest event" query returns the second Closing.
Depends: CLM-1 · Refs: OWL-Time, PROV-O · workflows-and-provenance · claim-competency-questions C9–C11
CLM-5

Migrate ClaimStatus from a gist:Category string enum to a governed SKOS scheme

High3 SPcodelist
As a vocabulary steward, I want claim statuses as skos:Concepts in a versioned scheme so that adding a status is a data release, not a schema release, and every value carries its business code and ACORD mapping.

Defect. ins:ClaimStatus ⊑ gist:Category hard-codes eight display strings ("Pending Review", "In Subrogation"…) into the schema — no IRIs, no skos:notation, no crosswalk; violates the project rule "code lists are reference data, model them as SKOS concept schemes and check allowed values with SHACL sh:in".

Fix. Mint https://data.chubb.com/vocabulary/ins/claim-status/ as a skos:ConceptScheme; one concept per value with prefLabel, notation, definition, topConceptOf, and skos:exactMatch/closeMatch to ACORD status codes. Re-range claim:hasClaimStatus to skos:Concept; enforce membership with SHACL sh:in (or inScheme check). Status remains a derived convenience of the CLM-4 event tail — "In Subrogation" is removed here and handled in CLM-12.

Acceptance criteria

  • AC: The scheme is published under the vocabulary namespace with dct:title, owl:versionInfo, and every concept carrying prefLabel + notation + definition.
  • AC: gist:Category-based ClaimStatus class and its named individuals are deprecated with dct:isReplacedBy per value.
  • AC: SHACL rejects a claim whose status IRI is outside the scheme (seeded bad-data case produces a named sh:Violation).
  • AC: At least three concepts carry documented ACORD crosswalk (skos:exactMatch or closeMatch with a mapping comment).
  • AC: Adding a test concept to the scheme requires no change to any owl:Class axiom (verified by diffing the schema module before/after).
Depends: CLM-4 · Refs: SKOS · rebuttal-codelist-skos · controlled-vocabularies
CLM-6

Unify claim-type code lists (ins:ClaimType, pandc:DOClaimType) into one SKOS pattern

Medium3 SPcodelist
As a data consumer joining ins: and pandc: data, I want one code-list representation for claim types so that the same question doesn't need two query shapes depending on which namespace wrote the data.

Defect. ins:ClaimType is a gist:Category string enum while pandc:DOClaimType is already skos:Concept — two patterns for the same kind of thing. The ins: list also mixes a process ("Subrogation") into a classification of loss nature.

Fix. Mint /vocabulary/ins/claim-type/ scheme; migrate ins: values (minus "Subrogation" → CLM-12); make DO claim types a narrower sub-collection (skos:broader) or a sibling scheme with skos:inScheme discipline; SHACL sh:in/scheme-membership on claim:hasClaimType.

Acceptance criteria

  • AC: One ConceptScheme governs claim types; DOClaimType concepts resolve into it via inScheme/broader without IRI changes to existing pandc data.
  • AC: "Subrogation" no longer appears as a claim-type value anywhere (query proof over the merged graph).
  • AC: SHACL validates type membership; the seeded wrong-scheme case fails with a named violation.
  • AC: A single SPARQL query returns the type distribution across both ins:- and pandc:-sourced sample claims.
Depends: CLM-5 pattern · Refs: SKOS mapping properties · taxonomy-trap
EPIC-CLM-B

Claim financials & relations — money that means something, properties that hold

Resolves F-02, F-03, F-07, F-08. FIBO's monetary-amount pattern; OWL property hygiene; the party-role pattern the ontology already owns.

CLM-7

Replace bare-decimal claim amounts with the MoneyAmount pattern (value + ISO-4217 currency)

High3 SPfinancials
As an actuarial analyst working a multi-currency book, I want every claim amount to carry its currency so that aggregations across USD, EUR and GBP claims cannot silently add unlike quantities.

Defect. paidAmount, reserveAmount, recoveryAmount, claimExpenseAmount (and ClaimPayment.paymentAmount) are naked xsd:decimal — "money flattened; the currency is lost" is verbatim the project's message-encoding antipattern. The Policy side already links ins:CurrencyCode, so the graph is internally inconsistent about money.

Fix. Introduce/reuse core:MoneyAmount (core:hasAmount xsd:decimal, core:hasCurrency → ISO-4217 skos:Concept), aligned skos:closeMatch to FIBO MonetaryAmount and schema:MonetaryAmount. Re-model claim amounts as object properties to MoneyAmount nodes. SHACL: every MoneyAmount has exactly one amount and one currency from the ISO scheme.

Acceptance criteria

  • AC: No datatype property with "amount" semantics remains on claim:Claim; each legacy decimal property is deprecated with dct:isReplacedBy.
  • AC: MoneyAmountShape (minCount/maxCount 1 for value and currency, currency in the ISO 4217 scheme) passes on good data and produces two named violations on the seeded currency-less amount.
  • AC: Crosswalk annotations to FIBO and schema.org are present on core:MoneyAmount.
  • AC: Multi-currency aggregation CQ groups paid totals by currency in the regression pack.
Refs: FIBO MonetaryAmount (EDM Council/OMG) · ISO 4217 · message-encoding-antipattern
CLM-8

Make claim financials valuation-dated; derive incurred instead of storing it

Medium3 SPfinancials
As a reserving actuary, I want paid/reserve figures tied to a valuation date and incurred computed so that loss development is reconstructible and the graph cannot assert paid + reserve ≠ incurred.

Defect. The current amounts are undated snapshots, and incurredAmount stores what its own definition says is "paid plus outstanding reserves" — redundant data that can (and in real feeds does) drift from its parts. Ironically ins:LossHistory already has valuationDate; first-class claims don't.

Fix. Add claim:FinancialSummary (Claim → 0..*) holding the four MoneyAmounts plus claim:valuationDate; the current summary is the latest date. Do not persist incurred; provide it as a SPARQL/inference view, and add a SHACL-SPARQL consistency constraint for any source that insists on shipping it.

Acceptance criteria

  • AC: FinancialSummary exists with exactly-one valuationDate (SHACL) and the four amount links ranging over MoneyAmount.
  • AC: No incurredAmount property remains in the module; the derivation query ships in the CQ pack and returns paid+reserve for the sample claim.
  • AC: Two summaries with different valuation dates coexist on one claim and the "latest summary" query picks the right one.
  • AC: SHACL-SPARQL check flags a seeded summary where a supplied incurred figure ≠ paid + reserve.
Depends: CLM-7 · Refs: OMG P&C valuation semantics · loss-run practice
CLM-9

Remove the hasClaimOnCoverage self-loop; declare each inverse pair once with owl:inverseOf

High3 SPproperty-hygiene
As an ontology engineer, I want broken and duplicated properties repaired so that traversals work in both directions from a single declared truth.

Defect. ins:hasClaimOnCoverage : Claim → Claim is a self-referencing property whose documentation ("Claims filed against this coverage") describes a Coverage→Claim link — a copy-paste domain error. Separately, Policy.hasClaim / Claim.claimOnPolicy and Claim.hasClaimPayment / ClaimPayment.paymentOnClaim are maintained as independent forward properties with no owl:inverseOf, so the pairs can (and will) drift.

Fix. Delete hasClaimOnCoverage (deprecate, dct:isReplacedBy claim:respondingCoverage's inverse). For each remaining pair keep one canonical direction (claim:underPolicy, claim:paymentOnClaim… ) and declare the other via a single owl:inverseOf axiom; document the canonical direction in an editorial note.

Acceptance criteria

  • AC: hasClaimOnCoverage is gone from the active module; the deprecation stub carries replacement guidance.
  • AC: Every claim-side bidirectional pair has exactly one owl:inverseOf axiom (SPARQL audit query over the schema returns the expected pair count, no orphans).
  • AC: Coverage→Claims traversal works via the inverse of claim:respondingCoverage in the regression pack.
  • AC: No object property in the claim module has identical domain and range unless intentionally reflexive and documented (audit query proves it).
Refs: OWL 2 §ObjectProperty axioms · rebuttal-domain-range
CLM-10

Reify claimant and adjuster as PartyRoles, consistent with the existing ins:PartyRole pattern

Medium3 SPparty-roles
As a claims data steward, I want claimant and adjuster modelled as roles with tenure so that adjuster reassignments and multi-claimant situations are representable without overwriting history.

Defect. claimantParty and hasAdjuster point straight at ins:Party, while the same ontology models Broker, Underwriter, NamedInsured and seven more as ins:PartyRole subclasses — the claim side simply skipped the house pattern. An adjuster handoff currently destroys the record of the previous adjuster.

Fix. Add claim:Claimant and claim:AdjusterAssignment ⊑ party:PartyRole, linked by claim:claimant / claim:hasAdjusterAssignment, each role carrying party:isPlayedBy → party:Party and an optional validity interval (OWL-Time). Claimant-differs-from-policyholder remains expressible and queryable.

Acceptance criteria

  • AC: Both role classes subclass the existing PartyRole hierarchy; direct Claim→Party properties are deprecated with replacements.
  • AC: Sample data shows one claim with two sequential AdjusterAssignments; "current adjuster" and "adjuster history" queries both succeed.
  • AC: SHACL requires each role instance to have exactly one isPlayedBy party.
  • AC: Third-party-claimant CQ (claimant ≠ policyholder) returns the seeded liability claim.
Refs: FIBO parties-in-roles · project party module role-holder pattern
CLM-11

Repair ClaimPayment: money pattern, mandatory claim link, populated PaymentType vocabulary

Medium3 SPfinancials
As a claims finance user, I want every payment tied to its claim with typed money and a coded payment type so that indemnity vs expense splits reconcile to claim totals.

Defect. paymentOnClaim is optional (payments floating free of any claim); paymentAmount is a bare decimal; ins:PaymentType is an empty category class — its "Indemnity, ALAE, Defense" values exist only in a comment.

Fix. SHACL exactly-one claim:paymentOnClaim; payment amount → MoneyAmount (CLM-7); payment date as xsd:date 1..1; mint /vocabulary/ins/claim-payment-type/ scheme (Indemnity, Expense-ALAE, Expense-ULAE, Defense, Recovery-Offset) with notations; payee via role (CLM-10 pattern).

Acceptance criteria

  • AC: Seeded orphan payment (no claim link) fails ClaimPaymentShape with a named violation; linked payments pass.
  • AC: The payment-type scheme exists with ≥5 concepts, each with prefLabel + notation + definition; sh:in/scheme check enforced.
  • AC: Payments-sum-by-type reconciliation query matches the claim's FinancialSummary paid total in sample data.
  • AC: Empty ins:PaymentType class is deprecated in favour of the scheme.
Depends: CLM-7, CLM-10 · Refs: ACORD payment/loss codes (as terminology)
CLM-12

Model subrogation as a real recovery process (one construct, not three)

Medium3 SPclaim-core
As a recovery specialist, I want subrogation modelled as a process with a target, amounts and outcome so that recovery performance is measurable, instead of subrogation being simultaneously a class, a claim type and a status.

Defect. ins:Subrogation is an empty class (zero properties); "Subrogation" is also a ClaimType value and "In Subrogation" a ClaimStatus value — the taxonomy trap: one process leaked into three modelling constructs, none usable.

Fix. Define claim:SubrogationProcess ⊑ core:Event (or Activity) with claim:subrogationOn (→ Claim, exactly 1 via SHACL), claim:againstParty (→ party role), recovered/expected MoneyAmounts, outcome from a small SKOS scheme; recovery events feed the CLM-4 lifecycle. Remove the type and status values (with CLM-5/6).

Acceptance criteria

  • AC: SubrogationProcess carries ≥4 properties (target claim, against-party, recovered amount, outcome) all SHACL-shaped.
  • AC: "Subrogation" absent from the claim-type scheme and "In Subrogation" from the status scheme; recovery state is derivable from process presence + outcome.
  • AC: Recovery-rate CQ (recovered / paid per claim with subrogation) computes on sample data.
  • AC: The old empty class is deprecated with dct:isReplacedBy.
Depends: CLM-5, CLM-6, CLM-7 · Refs: taxonomy-trap · ACORD subrogation terminology
EPIC-CLM-C

Claim integrity & governance — reinsurance, loss history, SHACL, namespaces, regression

Resolves F-06, F-09, F-10, F-11 and locks the refactor in with a competency-question regression pack.

CLM-13

Re-type ReinsuranceClaim as a Claim subclass with a mandatory originating claim

Medium3 SPreinsurance
As a ceded-re analyst, I want reinsurance recoveries modelled as claims that always trace to their originating claim so that gross-to-net calculations never hit a recovery with no source.

Defect. ins:ReinsuranceClaim ⊑ gist:Event repeats the F-01 conflation, its three amounts are bare decimals, and originatingClaim is optional — an unanchored recovery is representable.

Fix. claim:ReinsuranceClaim ⊑ claim:Claim (it is a demand — by the cedent, against the reinsurer, under a reinsurance agreement); SHACL exactly-one originatingClaim and exactly-one reinsuranceClaimOnAgreement; ceded/recovered amounts → MoneyAmount.

Acceptance criteria

  • AC: ReinsuranceClaim subclasses Claim and inherits ClaimShape validation plus its own shape for the two mandatory links.
  • AC: Seeded recovery without an originating claim fails with a named violation.
  • AC: Gross-vs-recovered CQ joins Claim → ReinsuranceClaim → ReinsuranceAgreement in the regression pack.
  • AC: Ceded amounts validate under MoneyAmountShape.
Depends: CLM-1, CLM-7, CLM-15 · Refs: reinsurance recovery practice · FIBO agreements
CLM-14

De-string LossHistory: link policy, claim, claimant and status by IRI, keep literals as source evidence

Medium3 SPloss-history
As an underwriter reviewing prior losses, I want loss-run records connected to the graph's parties and claims where they resolve so that a submission's loss history joins to the book instead of dead-ending in strings.

Defect. ins:LossHistory re-encodes its message: lossPolicyNumber, lossClaimNumber, claimantName, lossInsurer, claimStatus : xsd:string — five graph edges flattened to literals, duplicating the Claim financial fields under different names (claimPaidAmount vs paidAmount).

Fix. Add resolvable object properties (claim:aboutClaim 0..1, claim:underReportedPolicy 0..1, claimant role link, insurer → party) used when entity resolution succeeds; retain source literals explicitly annotated as evidence (dct:source-scoped), not as the model; status re-ranged to the CLM-5 scheme; financial fields re-based on MoneyAmount with valuationDate.

Acceptance criteria

  • AC: A loss-history record resolved to a known claim links by IRI, and the join "prior losses ↔ current claims for the same party" runs in one SPARQL query.
  • AC: Unresolved records still validate (object links optional), with literals flagged as source evidence via annotation.
  • AC: claimStatus string property is deprecated; the scheme-based property is SHACL-checked.
  • AC: No duplicated amount vocabulary remains: LossHistory reuses the same MoneyAmount properties as Claim (schema diff proves removal).
Depends: CLM-5, CLM-7 · Refs: message-encoding-antipattern §02 · user's loss-run JSON-LD work
CLM-15

Move all claim cardinalities into a SHACL shape suite; strip FunctionalProperty pseudo-validation

High3 SPshacl-governance
As a data-quality engineer, I want claim constraints expressed as SHACL shapes producing named violation reports so that bad feeds are rejected record-by-record instead of silently passing (open world) or poisoning the whole model (functional merges).

Defect. Mandatory/optional flags and owl:FunctionalProperty stamps do the validation job OWL cannot do: a missing claim number is "unknown", two claim numbers infer a merge or explode consistency. Meanwhile the real invariants (exactly one policy, exactly one loss, exactly one status) are enforced nowhere. This is the cardinality-rebuttal's exact prescription: shapes count, axioms classify.

Fix. Author shacl/claim-shapes.ttl: ClaimShape (claimId 1..1 + pattern; underPolicy 1..1; hasLoss 1..1; status 1..1 in-scheme), LossShape, ClaimEventShape, MoneyAmountShape, ClaimPaymentShape, FinancialSummaryShape, with sh:message and sh:severity throughout; remove FunctionalProperty from claim identifier/data properties; wire pySHACL into CI as a merge gate.

Acceptance criteria

  • AC: The suite validates the good sample dataset with conforms=true and reports ≥6 distinct named violations on the seeded bad dataset (orphan claim, duplicate claimId, currency-less amount, out-of-scheme status, orphan payment, event without timestamp).
  • AC: No owl:FunctionalProperty remains on claim-module datatype properties; retained functional object properties are justified in an editorial note.
  • AC: Every property shape carries a human-readable sh:message; severities distinguish Violation from Warning for migration-period rules.
  • AC: CI job runs pySHACL on every PR touching claim modules and fails on any Violation.
Refs: W3C SHACL · rebuttal-cardinality-restrictions (F3, F14) · deliverable shacl/claim-shapes.ttl
CLM-16

Re-mint claim-module IRIs to the data.chubb.com slash-namespace policy

Medium3 SPnamespace
As a platform owner, I want claim terms minted under the governed namespace scheme so that module ownership, versioning and dereferencing work the way every other conforming module does.

Defect. Current IRIs are https://chubb.com/ontology/ins#Claim: wrong host (policy mandates data.chubb.com), hash namespace (reserved for vendored W3C vocabularies), no layer/module path, and one flat namespace for 170 classes — NS-1 through NS-4 all violated.

Fix. Mint the refactored module at https://data.chubb.com/ontology/ins/claim/ (schema), /vocabulary/ins/claim-*/ (SKOS), /resource/… (instances), trailing slashes, owl:Ontology IRI = namespace, versionIRI = namespace+version (NS-5/6/8), prefix claim everywhere (NS-7). Publish an old→new skos:exactMatch/owl:equivalentClass bridge file and deprecation timeline for the legacy hash IRIs.

Acceptance criteria

  • AC: Every term in the delivered TTL/JSON-LD resolves under the new scheme; an automated audit finds zero chubb.com/ontology/ins# IRIs in the active module.
  • AC: Ontology header satisfies NS-5/6/8 (baseURI, preferred prefix, versionIRI all consistent) — checked by script.
  • AC: The bridge file maps 100% of migrated claim-side terms old→new; loading legacy data + bridge answers new-namespace queries.
  • AC: Vocabulary and instance IRIs use /vocabulary/ and /resource/ roots respectively.
Refs: namespace-policy NS-1..NS-8 · extension-authoring-guide §04 · ontology-versioning
CLM-17

Build the claim competency-question regression pack (SPARQL, CI-run)

Medium3 SPverification
As the ontology team, we want every claim CQ runnable against sample data on every change so that the refactor is proven to answer the questions the model exists for — and stays that way.

Defect. The project defines 14 claim CQs with expected answers, but nothing executes them against this delivery; several (reopen history, status-from-events, exactly-one-policy validation) are provably unanswerable today.

Fix. Port CQ-C1..C14 to the refactored namespace; add new CQs for cat aggregation (CLM-3), multi-currency totals (CLM-7), adjuster history (CLM-10), recovery rate (CLM-12), gross-to-net (CLM-13). Ship resource/claim-example.ttl with expected result fixtures; run in CI beside the SHACL gate; enforce the reverse check — every claim-module term appears in ≥1 CQ or is flagged.

Acceptance criteria

  • AC: All 14 original CQs return their documented expected answers over the sample dataset.
  • AC: ≥5 new CQs cover the refactored areas, each with fixture-checked results.
  • AC: Reverse-coverage report lists any term not exercised by a CQ; delivery has zero unexercised claim-module terms (or documented waivers).
  • AC: CI fails when a schema change breaks any CQ fixture.
Depends: CLM-1..CLM-15 · Refs: competency-questions method (forward + reverse coverage)
EPIC-POL-D

Policy & cross-cutting — after the claim side, the same medicine elsewhere

Resolves F-12, F-13, F-14. Sequenced after the claim epics so the patterns (SKOS lists, MoneyAmount, SHACL suite, event modelling) are established and reused.

POL-1

Fix the resultsInPolicy domain error (belongs on Quote, not Policy)

High3 SPproperty-hygiene
As an underwriting analyst, I want the quote→policy binding link declared on Quote so that bind-ratio traversals don't rely on a property whose own domain contradicts its definition.

Defect. ins:resultsInPolicy — "Quote that produced this policy when bound" — is declared on ins:Policy with range ins:Policy, creating a meaningless Policy→Policy edge; Quote itself lacks the property. The redundant originatedFromQuote exists on Policy with no inverse axiom tying the two.

Fix. Re-declare resultsInPolicy : ins:Quote → ins:Policy, one owl:inverseOf originatedFromQuote axiom, SHACL max 1 on each end; migrate any instance data; audit the full schema for other properties whose textual definition names a different domain than declared (the pattern that produced this bug).

Acceptance criteria

  • AC: resultsInPolicy has domain Quote / range Policy and a single inverse axiom; the Policy→Policy declaration is gone.
  • AC: Bind-ratio CQ (quotes → bound policies per account) runs on sample data.
  • AC: A definition-vs-domain audit script has been run over all 696 relations; mismatches are logged as new backlog items.
  • AC: SHACL rejects a quote binding to two policies.
Refs: rebuttal-domain-range · OWL 2
POL-2

Decompose the Policy god-class into cohesive modules

Medium3 SPmodularization
As an ontology maintainer, I want Policy's ~60 direct properties factored into cohesive sub-structures so that multinational, distribution and financial concerns evolve independently instead of churning one giant class.

Defect. ins:Policy mixes contract identity, lifecycle dates, premium, cancellation, multinational programme codes, broker branch strings, London-market fields, FX and commission — a god-class the modularization brief exists to prevent.

Fix. Factor into linked aggregates: policy:MultinationalProgramme (master/local linkage replacing multinationalPolicyCode strings), policy:DistributionArrangement (broker, branch, commission), policy:SubscriptionShare (writtenLinePercentage, placementPosition, leader relations), keeping Policy itself to identity + term + parties + coverages. Deprecate moved properties with replacement annotations; one migration CONSTRUCT per aggregate.

Acceptance criteria

  • AC: Policy's direct property count drops below 25, with every moved property deprecated-with-replacement (audit query).
  • AC: The three aggregates exist with SHACL shapes and are reachable from Policy by exactly-one/0..* links as documented.
  • AC: Master↔local multinational traversal works by IRI, not code string, in sample data.
  • AC: All existing policy CQs in the project pack still pass post-refactor.
Refs: ontology-modularization · schema.org property-cluster practice
POL-3

Model cancellation as a lifecycle event, not four scattered literals

Medium3 SPpolicy-lifecycle
As a policy-operations analyst, I want cancellations as first-class events with type, date, reason and notice terms so that cancellation analytics and reinstatement histories are queryable.

Defect. policyCancellationDate, cancellationDescription, cancellationTypeCode (a bare string code) and cancellationNoticeDays sit flat on Policy; a reinstated-then-recancelled policy is unrepresentable — the same defect CLM-4 fixes for claims.

Fix. policy:CancellationEvent ⊑ core:Event with OWL-Time instant, reason from a /vocabulary/ins/cancellation-type/ SKOS scheme, notice-days on the event; add policy:ReinstatementEvent; derive the "cancelled" status view; deprecate the four literals.

Acceptance criteria

  • AC: Cancel → reinstate → cancel history round-trips on a sample policy; latest-event status query returns cancelled.
  • AC: Cancellation types are SKOS concepts with notations mapped from legacy code strings (100% of observed codes).
  • AC: The four legacy literals are deprecated with dct:isReplacedBy.
  • AC: SHACL: every CancellationEvent has exactly one timestamp and one typed reason.
Depends: CLM-4 pattern, CLM-5 pattern · Refs: OWL-Time · workflows-and-provenance
POL-4

Normalise stringly-typed Policy classifications (policyType, language duplicates)

Medium3 SPcodelist
As a reporting consumer, I want policy classifications drawn from governed vocabularies so that "policy type" means the same value set in every dataset.

Defect. policyType : xsd:string free text sits beside a dozen properly-linked classification NIs; policyLanguage ("French") and policyLanguageCode ("FR") duplicate one fact in two uncoordinated literals instead of one ISO-639 concept link.

Fix. /vocabulary/ins/policy-type/ SKOS scheme + hasPolicyType object property; replace both language literals with hasPolicyLanguage → ISO-639 concept (label and code become prefLabel and notation of the concept); deprecate the literals; sh:in checks.

Acceptance criteria

  • AC: policyType, policyLanguage, policyLanguageCode are deprecated; replacement object properties are SHACL-validated against their schemes.
  • AC: ISO-639 scheme concepts carry both the display name and the 2-letter code (prefLabel/notation), covering every language value observed in the current model.
  • AC: A language-distribution query over sample policies groups by concept IRI, not string.
  • AC: No new free-text classification properties are introduced (schema audit).
Depends: CLM-5 pattern · Refs: ISO 639 · rebuttal-codelist-skos
POL-5

Anchor untyped root classes (InsuranceCoverage, CoveredPeril, …) to the upper ontology

Medium3 SPupper-alignment
As an ontology engineer, I want every class to declare its upper-ontology parent so that generic tooling (event views, category views, agreement views) covers the whole model, not just the aligned parts.

Defect. 62 classes — including load-bearing ones like ins:InsuranceCoverage and ins:CoveredPeril — sit directly under owl:Class with no gist alignment, while their neighbours are aligned; "Abstract base class" appears only as description text with no isAbstract semantics.

Fix. Classify the 62 roots: coverages under the agreement/commitment branch, perils as SKOS peril scheme (they are code-like) or core:Category, genuinely abstract classes annotated and SHACL-guarded against direct instantiation. Produce a decision table (class → parent → rationale) reviewed with the team.

Acceptance criteria

  • AC: Zero classes remain with owl:Class as their only parent (audit query on the schema).
  • AC: The decision table covering all 62 classes is committed alongside the change, each row citing the applied test (structure-bearing class vs code value vs role).
  • AC: Perils resolve as a SKOS scheme consistent with CLM-5/6 patterns, with claim/Loss and ReinsuranceAgreement links re-ranged accordingly.
  • AC: Reasoner run is consistent; no class is inferred equivalent to owl:Thing.
Refs: gist · taxonomy-trap · reusing-standard-ontologies
POL-6

Close known viewer violations: Endorsement→Policy link and audit-trail provenance

Medium3 SPprovenance
As a compliance reviewer, I want endorsements traversable to the policy they modify and creation/change provenance on Policy and Submission so that the delivery's own flagged violations V-023 and V-013 are closed.

Defect. Viewer-flagged: ins:Endorsement has modifiesCoverage but no property reaching its Policy (V-023, HIGH); Policy and Submission carry no createdBy/modified provenance while Quote does (V-013) — and Quote's are strings, not party links (the V-017 pattern).

Fix. Add policy:endorsesPolicy (Endorsement → Policy, exactly 1 via SHACL) with inverse hasEndorsement; replace string audit fields with PROV-O (prov:wasGeneratedBy activity, prov:wasAssociatedWith party, prov:generatedAtTime) applied uniformly to Policy, Submission, Quote and Endorsement.

Acceptance criteria

  • AC: Endorsement→Policy traversal returns the policy for every endorsement in sample data; orphan endorsement fails SHACL.
  • AC: Policy/Submission/Quote/Endorsement all carry the same PROV-O provenance pattern; string audit properties are deprecated.
  • AC: "Who changed this policy and when" CQ answers via PROV-O chain.
  • AC: Viewer violations V-013, V-017 and V-023 are demonstrably closed against the refactored module (re-run of the violation checks).
Refs: W3C PROV-O · viewer VIOLATION_DESC V-013/V-017/V-023